using System;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.Serialization;
using System.Security.Permissions;
using Microsoft.WowAddonStudio.FrameXml.Serialization;

namespace Microsoft.WowAddonStudio.FrameXml.Components.Support
{
	public enum ScriptType
	{
		Undefined,
		Movable,
		Resizable
	}

	/// <summary>
	/// Injects specified scripts into FrameXml file.
	/// </summary>
	public static class ScriptInjector
	{
		private const string MovablePropertyName = "Movable";
		private const string ResizablePropertyName = "Resizable";

		/// <summary>
		/// Removes the script.
		/// </summary>
        /// <param name="frameControl">The frame XML control.</param>
        /// <param name="eventName">The frame XML event.</param>
		public static void RemoveScript(FrameControl frameControl, EventName eventName)
		{
            if (frameControl == null)
                throw new ArgumentNullException("frameControl");

            frameControl.ScriptContainer.Remove(eventName);
		}

		/// <summary>
		/// Injects the script.
		/// </summary>
		/// <param name="script">The script.</param>
		/// <param name="eventName">The frame XML event.</param>
		/// <param name="frameControl">The frame XML control.</param>
        public static void InjectScript(FrameControl frameControl, EventName eventName, string script)
		{
            if (frameControl == null)
                throw new ArgumentNullException("frameControl");

            if (frameControl.ScriptContainer.Contains(eventName))
                frameControl.ScriptContainer[eventName].ScriptBlock = script;
			else
                frameControl.ScriptContainer[eventName] =
					new Script(eventName) { ScriptBlock = script };
		}

		/// <summary>
		/// Injects the specified script to the frame xml.
		/// </summary>
		/// <param name="scriptType">Type of the script.</param>
        /// <param name="frameControl">The frame XML control.</param>
        public static MemberDescriptor InjectScript(FrameControl frameControl, ScriptType scriptType)
		{
            if (frameControl == null)
                throw new ArgumentNullException("frameControl");

			try
			{
				switch (scriptType)
				{
					case ScriptType.Movable:
                        return InjectMovable(frameControl);
					case ScriptType.Resizable:
                        return InjectResizable(frameControl);
				}
			}
			catch (Exception e)
			{
				throw new ScriptInjectionException(scriptType, e);
			}

			return null;
		}

		/// <summary>
		/// Injects the movable.
		/// </summary>
        /// <param name="frameControl">The frame XML control.</param>
        private static MemberDescriptor InjectMovable(FrameControl frameControl)
		{
            var property = TypeDescriptor.GetProperties(frameControl.SerializationObject)[MovablePropertyName];

			if (property != null)
			{
				//string name = ((INamedObject) frameXmlControl.SerializationObject).Name;
				string regScript = Resources.Scripts_RegisterMoveableObject;


                if (!IsMovingEnabled(frameControl))
				{
					//Add movable frame specified scripts to frameXml
                    AddScript(frameControl.ScriptContainer, EventName.OnLoad, regScript);
                    AddScript(frameControl.ScriptContainer, EventName.OnDragStart,
					          Resources.Scripts_StartMoveScript);
                    AddScript(frameControl.ScriptContainer, EventName.OnDragStop,
					          Resources.Scripts_StopMoveScript);
                    property.SetValue(frameControl, true);
				}
				else
				{
					//Remove only movable frame specified scripts to frameXml
                    RemoveScript(frameControl.ScriptContainer, EventName.OnLoad,
					             regScript);
                    RemoveScript(frameControl.ScriptContainer, EventName.OnDragStart,
					             Resources.Scripts_StartMoveScript);
                    RemoveScript(frameControl.ScriptContainer, EventName.OnDragStop,
					             Resources.Scripts_StopMoveScript);
                    property.SetValue(frameControl, false);
				}
			}

			return property;
		}

		/// <summary>
		/// Injects the resizable.
		/// </summary>
        /// <param name="frameControl">The frame XML control.</param>
		private static MemberDescriptor InjectResizable(FrameControl frameControl)
		{
            var property = TypeDescriptor.GetProperties(frameControl.SerializationObject)[ResizablePropertyName];
			if (property != null)
			{
				//NOTE: add and remove script if needed
			}

			return property;
		}


		/// <summary>
		/// Get the specified script type.
		/// </summary>
        /// <param name="frameControl">The frame XML control.</param>
		/// <returns>
		/// 	<c>true</c> if [is moving enabled] [the specified frame XML control]; otherwise, <c>false</c>.
		/// </returns>
        public static bool IsMovingEnabled(FrameControl frameControl)
		{
            if (frameControl == null)
                throw new ArgumentNullException("frameControl");

			var property = TypeDescriptor.GetProperties(frameControl.SerializationObject)[MovablePropertyName];
		    if (property != null)
			{
                return (bool)property.GetValue(frameControl) && frameControl.ScriptContainer.Contains(EventName.OnDragStart) &&
                       frameControl.ScriptContainer[EventName.OnDragStart].
				       	ScriptBlock.Contains(Resources.Scripts_StartMoveScript) &&
                       frameControl.ScriptContainer.Contains(EventName.OnDragStart) &&
                       frameControl.ScriptContainer[EventName.OnDragStop].
				       	ScriptBlock.Contains(Resources.Scripts_StopMoveScript);
			}

			return false;
		}

		/// <summary>
		/// Adds the script.
		/// </summary>
		/// <param name="scriptContainer">The script container.</param>
		/// <param name="eventName">Name of the event.</param>
		/// <param name="script">The script.</param>
		private static void AddScript(ScriptContainer scriptContainer, EventName eventName, string script)
		{
			Script eventScript = scriptContainer[eventName];
			if (eventScript == null)
			{
				eventScript = new Script(eventName) {ScriptBlock = script};
				scriptContainer.Add(eventName, eventScript);
			}
			else
			{
				if (!string.IsNullOrEmpty(eventScript.ScriptBlock))
					eventScript.ScriptBlock = eventScript.ScriptBlock.Trim();

				eventScript.ScriptBlock = String.Concat(eventScript.ScriptBlock
				                                        , Environment.NewLine, script);
			}
		}

		/// <summary>
		/// Removes the script.
		/// </summary>
		/// <param name="scriptContainer">The script container.</param>
		/// <param name="eventName">Name of the event.</param>
		/// <param name="script">The script.</param>
		private static void RemoveScript(ScriptContainer scriptContainer, EventName eventName, string script)
		{
			Script eventScript = scriptContainer[eventName];
			if (eventScript != null)
			{
				if (eventScript.ScriptBlock == script)
				{
					scriptContainer.Remove(eventName);
				}
				else
				{
					eventScript.ScriptBlock = eventScript.ScriptBlock.Replace(script, string.Empty);
					if (!string.IsNullOrEmpty(eventScript.ScriptBlock))
						eventScript.ScriptBlock = eventScript.ScriptBlock.Trim();
				}
			}
		}


		/// <summary>
		/// Represents errors that occur during script injection execution.
		/// </summary>
		[Serializable]
		public class ScriptInjectionException : Exception
		{
			/// <summary>
			/// Gets or sets the type of the script.
			/// </summary>
			/// <value>The type of the script.</value>
			public ScriptType ScriptType { get; protected set; }

			/// <summary>
			/// Initializes a new instance of the <see cref="ScriptInjectionException"/> class.
			/// </summary>
			public ScriptInjectionException()
				: this(ScriptType.Undefined)
			{
			}

			/// <summary>
			/// Initializes a new instance of the <see cref="ScriptInjectionException"/> class.
			/// </summary>
			/// <param name="scriptType">The <see cref="ScriptType"/> parameter.</param>
			public ScriptInjectionException(ScriptType scriptType)
				: this(scriptType, Resources.ScriptInjectionExceptionMessage, null)
			{
			}

			/// <summary>
			/// Initializes a new instance of the <see cref="ScriptInjectionException"/> class.
			/// </summary>
			/// <param name="scriptType">The <see cref="ScriptType"/> parameter.</param>
			/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
			public ScriptInjectionException(ScriptType scriptType, Exception innerException)
				: this(scriptType, Resources.ScriptInjectionExceptionMessage, innerException)
			{
			}

			/// <summary>
			/// Initializes a new instance of the <see cref="ScriptInjectionException"/> class
			///  with a specified error message.
			/// </summary>
			/// <param name="scriptType">The <see cref="ScriptType"/> parameter.</param>
			/// <param name="message">The message that describes the error.</param>
			public ScriptInjectionException(ScriptType scriptType, string message)
				: this(scriptType, message, null)
			{
			}

			/// <summary>
			/// Initializes a new instance of the <see cref="ScriptInjectionException"/> class with a specified error message
			/// and a reference to the inner exception that is the cause of this exception.
			/// </summary>
			/// <param name="scriptType">The <see cref="ScriptType"/> parameter.</param>
			/// <param name="message">The message that describes the error.</param>
			/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
			public ScriptInjectionException(ScriptType scriptType, string message, Exception innerException)
				: base(message, innerException)
			{
				ScriptType = scriptType;
			}

			/// <summary>
			/// Initializes a new instance of the <see cref="ScriptInjectionException"/> class with serialized data.
			/// </summary>
			/// <param name="info">The <see cref="System.Runtime.Serialization.SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param>
			/// <param name="context">The <see cref="System.Runtime.Serialization.StreamingContext"/> that contains contextual information about the source or destination.</param>
			protected ScriptInjectionException(SerializationInfo info, StreamingContext context)
				: base(info, context)
			{
				if (info != null)
					ScriptType = (ScriptType) Enum.Parse(typeof (ScriptType), info.GetString("ScriptType"));
			}

			/// <summary>
			/// When overridden in a derived class, sets the System.Runtime.Serialization.SerializationInfo with information about the exception.
			/// </summary>
			/// <param name="info"></param>
			/// <param name="context"></param>
			[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
			public override void GetObjectData(SerializationInfo info, StreamingContext context)
			{
				base.GetObjectData(info, context);
				info.AddValue("ScriptType", ScriptType);
			}

			/// <summary>
			/// Gets a message that describes the current exception.
			/// </summary>
			/// <value></value>
			/// <returns>The error message that explains the reason for the exception, or an empty string("").</returns>
			public override string Message
			{
				get { return String.Format(CultureInfo.InvariantCulture, base.Message, ScriptType); }
			}
		}
	}
}